package com.dongrj.framework.starter.log.appender;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import com.dongrj.framework.core.constant.Constant;
import com.dongrj.framework.core.utils.AppUtils;
import com.dongrj.framework.core.utils.DateUtils;
import com.dongrj.framework.core.utils.StringUtils;
import com.dongrj.framework.starter.log.constant.LogConstant;
import com.dongrj.framework.starter.log.model.ELKModel;
import com.dongrj.framework.starter.log.send.DefaultElkSend;
import com.dongrj.framework.starter.log.send.KafkaElkSend;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.util.Date;

/**
 * @author dongrongjun
 * @date 2020/08/04
 */
@Slf4j
public class MyLogbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private Layout<ILoggingEvent> layout;

    private String elkEnv = System.getenv("APP_ENV");


    @Override
    public void start() {
        if (layout == null) {
            addWarn("Layout was not defined!");
        }
        super.start();
    }


    @Override
    public void stop() {
        if (!isStarted()) {
            return;
        }
        super.stop();
    }

    @Override
    public void append(ILoggingEvent event) {
        if (StringUtils.equals(elkEnv, "dev")) {
            return;
        }
        if (event == null || !isStarted()) {
            return;
        }
        ELKModel elkModel = getElkMsg(event);
        serviceExceptionErrorToWarn(elkModel, event);
        KafkaElkSend.getInstance().send(elkModel);

        if ("ERROR".equals(elkModel.getLevel())) {
            DefaultElkSend.getInstance().send(elkModel);
        }
    }


    private ELKModel getElkMsg(ILoggingEvent event) {
        ELKModel model = new ELKModel();
        // 应用ID
        model.setAppId(AppUtils.getAppId());
        // 接口名称
        model.setTraceId(MDC.get(Constant.TRACE_ID));
        // 被调用方IP
        model.setServerIp(AppUtils.getLocalIP());
        // 异常信息
        model.setException(getException(event));
        // 日志内容，有分词处理
        model.setData(event.getFormattedMessage());
        // 日志记录器名称
        model.setLoggerName(event.getLoggerName());
        // 级别
        model.setLevel(event.getLevel().levelStr);
        // 用户ID
        model.setUserId(MDC.get(LogConstant.MDCField.USER_ID));
        // 日志时间
        model.setLogTime(DateUtils.toString(new Date()));
        // 日志长度
        model.setLogLength(StringUtils.length(model.getData()));
        // 请求地址
        model.setUri(MDC.get(LogConstant.MDCField.URI));
        return model;
    }

    private String getException(ILoggingEvent event) {
        IThrowableProxy throwableProxy = event.getThrowableProxy();
        if (throwableProxy != null) {
            return ThrowableProxyUtil.asString(throwableProxy);
        }
        return null;
    }

    private String getExceptionName(ILoggingEvent event) {
        IThrowableProxy throwableProxy = event.getThrowableProxy();
        if (throwableProxy != null) {
            return event.getThrowableProxy().getClassName();
        }
        return "none";
    }

    private void serviceExceptionErrorToWarn(ELKModel elkModel, ILoggingEvent event) {
        if (StringUtils.equals(elkModel.getLevel(), "ERROR")) {
            if (event.getThrowableProxy() != null) {
                String className = event.getThrowableProxy().getClassName();
                if (isServiceException(className)) {
                    elkModel.setLevel("WARN");
                }
            }
        }
    }

    private boolean isServiceException(String exceptionClassName) {
        if (exceptionClassName.endsWith("ServiceException")) {
            return true;
        }
        if (exceptionClassName.endsWith("MethodArgumentNotValidException")) {
            return true;
        }
        return false;
    }


    public Layout<ILoggingEvent> getLayout() {
        return layout;
    }

    public void setLayout(Layout<ILoggingEvent> layout) {
        this.layout = layout;
    }

    public String getElkEnv() {
        return elkEnv;
    }

    public void setElkEnv(String elkEnv) {
        this.elkEnv = elkEnv;
    }
}
